namespace NBody.Simulation
{
    using System;
            
    public abstract class Base
    {
        public event OutputEventHandler OutputEvent;
        protected virtual void OnOutputEvent(int nsteps, double time)
        {
            if (OutputEvent != null)
                OutputEvent(this, new OutputEventArgs(nsteps, system));
        }
        
        public event DiagnosticEventHandler DiagnosticEvent;
        protected virtual void OnDiagnosticEvent(int nsteps, double time, double e_pot, TimeSpan ts_step, TimeSpan ts_total)
        {
            if (DiagnosticEvent != null)
            {
                double e_kin = 0.0;
                foreach (Particle p in system)
                    e_kin += 0.5 * p.Mass * p.VelocitySquared;
                DiagnosticEvent(this, new DiagnosticEventArgs(nsteps, time, e_kin, e_pot, ts_step, ts_total));
            }
        }
        
        protected NBodySystem system;
        
        protected double dt_param;  // timestep length
        protected double dt_diag;   // diagnostics interval
        protected double dt_out;    // output interval
        protected double t_stop;    // stop time
        
        public Base()
        {
            dt_param = 0.03;
            dt_diag = 1.0;
            dt_out = 1.0;
            t_stop = 10.0;
        }
        
        public double TimeStepControlParam
        {
            get { return dt_param; }
            set { dt_param = value; }
        }
        
        public double DiagnosticsInterval
        {
            get { return dt_diag; }
            set { dt_diag = value; }
        }
        
        public double OutputInterval
        {
            get { return dt_out; }
            set { dt_out = value; }
        }
        
        public double StopTime
        {
            get { return t_stop; }
            set { t_stop = value; }
        }       
        
        public void Evolve(NBodySystem s)
        {
            this.system = s;
            double epot, coll_time;
            
            DateTime dt_start = DateTime.Now;
            Initialize(out epot, out coll_time);
            
            TimeSpan ts_step = DateTime.Now - dt_start;
            TimeSpan ts_total = DateTime.Now - dt_start;
            OnDiagnosticEvent(0, s.Time, epot, ts_step, ts_total);
            
            int nsteps = 0;
            double t = s.Time;
            double t_out = t + dt_out;
            double t_diag = t += dt_diag;
            
            while (t <= t_stop)
            {
                DateTime dt_step_start = DateTime.Now;
                
                double dt = coll_time * dt_param;
                Iterate(dt, out epot, out coll_time);
                t += dt;
                s.SetTime(t);
                nsteps++;
                
                ts_step = DateTime.Now - dt_step_start;
                ts_total = DateTime.Now - dt_start;
                
                if (t >= t_out)
                {
                    OnOutputEvent(nsteps, t);
                    t_out += dt_out;
                }
                if (t >= t_diag)
                {
                    OnDiagnosticEvent(nsteps, t, epot, ts_step, ts_total);
                    t_diag += dt_diag;
                }                
            }
        }
        
        protected abstract void Initialize(out double epot, out double coll_time);
        
        protected abstract void Iterate(double dt, out double epot, out double coll_time);
    }
}
